/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.svn.server;


import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Checks Subversion configuration to ensure properly work SVN-extension in Che.
 * <p/>
 * Here we check that we have correct setting for svn ignore.
 * Must be ignored files:
 * <pre>.che
 *  .vfs</pre>
 *
 * @author Vladyslav Zhukovskyi
 */
@Singleton
public class SubversionConfigurationChecker {

    private static final Set<String> SUBVERSION_IGNORE_PATTERNS = new LinkedHashSet<>();

    static {
        SUBVERSION_IGNORE_PATTERNS.add(".che");
        SUBVERSION_IGNORE_PATTERNS.add(".vfs");
    }

    private static final Logger LOG = LoggerFactory.getLogger(SubversionConfigurationChecker.class);

    private final Path GLOBAL_SUBVERSION_CONFIG_FILE_PATH;

    protected SubversionConfigurationChecker() {
        GLOBAL_SUBVERSION_CONFIG_FILE_PATH = Paths.get(System.getProperty("user.home") + "/.subversion/config");
    }

    // Constructor need for unit-tests
    protected SubversionConfigurationChecker(Path subversionConfigFile) {
        GLOBAL_SUBVERSION_CONFIG_FILE_PATH = subversionConfigFile;
    }

    @PostConstruct
    public void start() {
        try {
            ensureExistingSvnConfigFile();
            checkAndUpdateConfigFile();
        } catch (IOException e) {
            LOG.error(e.getMessage(), e);
        }
    }

    /**
     * Return loaded configuration file.
     *
     * @return configuration file
     */
    protected Path getLoadedConfigFile() {
        return GLOBAL_SUBVERSION_CONFIG_FILE_PATH;
    }

    /**
     * Checks if Subversion configuration file exist and if none, then tries to create it.
     *
     * @throws IOException
     *         if creation was failed
     */
    protected void ensureExistingSvnConfigFile() throws IOException {
        if (Files.notExists(GLOBAL_SUBVERSION_CONFIG_FILE_PATH)) {
            Path parent = GLOBAL_SUBVERSION_CONFIG_FILE_PATH.getParent();
            if (Files.notExists(parent)) {
                Files.createDirectories(parent);
            }
            Files.createFile(GLOBAL_SUBVERSION_CONFIG_FILE_PATH);
        }
    }

    /**
     * Checks existing config file for global-ignore property filling and concatenate system default values need to proper work of Che.
     * <p/>
     * .che and .vfs directories should be added to global-ignore section in SVN configuration.
     *
     * @throws IOException
     *         if processing of config file was failed
     */
    protected void checkAndUpdateConfigFile() throws IOException {
        List<String> subversionConfigContent = Files.readAllLines(GLOBAL_SUBVERSION_CONFIG_FILE_PATH, Charset.forName("UTF-8"));

        int miscellanySectionIndex = -1;
        int globalIgnoresIndex = -1;
        boolean ignoreSectionCommented = true;

        for (int i = 0; i < subversionConfigContent.size(); i++) {
            String line = subversionConfigContent.get(i);

            if (line.startsWith("[miscellany]")) {
                miscellanySectionIndex = i;
            } else if (line.trim().startsWith("# global-ignores")) {
                globalIgnoresIndex = i;
                ignoreSectionCommented = true;
                break;
            } else if (line.trim().startsWith("global-ignores")) {
                globalIgnoresIndex = i;
                ignoreSectionCommented = false;
                break;
            }
        }

        if (miscellanySectionIndex == -1) { //if this section doesn't exist
            subversionConfigContent.add("[miscellany]");
            subversionConfigContent.add("global-ignores = " + getDefaultExcludes());
        } else if (globalIgnoresIndex == -1) { //in case if misc section exists but config parameter isn't
            subversionConfigContent.add(miscellanySectionIndex, "global-ignores = " + getDefaultExcludes());
        } else {
            String configParameter = subversionConfigContent.get(globalIgnoresIndex);
            if (ignoreSectionCommented) {
                configParameter = configParameter.substring(2).concat(" ").concat(getDefaultExcludes());
            } else {
                Iterable<String> filteredExcludes = filterExistingExcludes(configParameter, SUBVERSION_IGNORE_PATTERNS);
                configParameter = configParameter.concat(" ").concat(Joiner.on(" ").join(filteredExcludes));
            }

            subversionConfigContent.set(globalIgnoresIndex, configParameter);
        }

        updateConfigFile(Joiner.on('\n').join(subversionConfigContent), GLOBAL_SUBVERSION_CONFIG_FILE_PATH);
    }

    /**
     * Writes content to file.
     *
     * @param content
     *         file content
     * @param configFile
     *         file path
     * @throws IOException
     *         if write was failed
     */
    private void updateConfigFile(String content, Path configFile) throws IOException {
        Files.write(configFile, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
    }

    /**
     * Return default excludes string.
     *
     * @return string which contains excludes
     */
    protected String getDefaultExcludes() {
        return Joiner.on(' ').join(SUBVERSION_IGNORE_PATTERNS);
    }

    private Iterable<String> filterExistingExcludes(String configLine, final Set<String> defaultExcludes) {
        if (!configLine.contains("=")) {
            throw new IllegalStateException("Wrong configuration parameter");
        }

        final String values = configLine.substring(configLine.indexOf('=') + 1).trim();
        if (values.isEmpty()) {
            return defaultExcludes;
        }

        return Iterables.filter(defaultExcludes, new Predicate<String>() {
            @Override
            public boolean apply(String defValue) {
                List<String> existValues = Splitter.on(' ').splitToList(values);
                return !existValues.contains(defValue);
            }
        });
    }
}
